package com.github.florent37.shapeofview.shapes;


import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.render.BlendMode;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.Path;
import ohos.agp.utils.Color;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;

import com.github.florent37.shapeofview.ShapeOfView;
import com.github.florent37.shapeofview.manager.ClipPathManager;

/**
 * RoundRectView
 *
 * @since 2021-07-26
 */
public class RoundRectView extends ShapeOfView {

    private final RectFloat rect = new RectFloat();
    //region border
    private final Paint borderPaint = new Paint();
    private final RectFloat borderRect = new RectFloat();
    private final Path borderPath = new Path();
    private float topLeftRadius = 0f;
    private float topRightRadius = 0f;
    private float bottomRightRadius = 0f;
    private float bottomLeftRadius = 0f;
    private Color borderColor = Color.WHITE;
    private float borderWidthPx = 0f;
    private DrawTask mDrawTask = new DrawTask() {
        @Override
        public void onDraw(Component component, Canvas canvas) {
            if (borderWidthPx > 0) {
                borderPaint.setStrokeWidth(borderWidthPx);
                borderPaint.setStyle(Paint.Style.FILL_STYLE);
                borderPaint.setColor(borderColor);
//                canvas.drawPath(borderPath, borderPaint);

//                canvas.drawColor(0xffff0000, BlendMode.SRC);

                RectFloat rect = new RectFloat();
                rect.left = 0;
                rect.top = 0;
                rect.right = getWidth();
                rect.bottom = getHeight();
                int layer = canvas.saveLayer(rect, new Paint());

                canvas.drawColor(0xffffffff, BlendMode.SRC);
                borderPaint.setBlendMode(BlendMode.CLEAR);
                canvas.drawPath(borderPath, borderPaint);
                borderPaint.setBlendMode(null);

                canvas.restoreToCount(layer);

                borderPaint.setStyle(Paint.Style.STROKE_STYLE);
                canvas.drawPath(borderPath, borderPaint);
            }
        }
    };

    /**
     * RoundRectView
     *
     * @param context context
     */
    public RoundRectView(Context context) {
        super(context);
        init(context, null);
    }

    /**
     * RoundRectView
     *
     * @param context context
     * @param attrs attrs
     */
    public RoundRectView(Context context, AttrSet attrs) {
        super(context, attrs);
        init(context, attrs);
        addDrawTask(mDrawTask);
    }

    /**
     * RoundRectView
     *
     * @param context context
     * @param attrs attrs
     * @param defStyleAttr defStyleAttr
     */
    public RoundRectView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
        addDrawTask(mDrawTask);
    }

    private void init(Context context, AttrSet attrs) {
        if (attrs != null) {
            topLeftRadius = attrs.getAttr("shape_roundRect_topLeftRadius").get().getFloatValue();
            topRightRadius = attrs.getAttr("shape_roundRect_topRightRadius").get().getFloatValue();
            bottomLeftRadius = attrs.getAttr("shape_roundRect_bottomLeftRadius").get().getFloatValue();
            bottomRightRadius = attrs.getAttr("shape_roundRect_bottomRightRadius").get().getFloatValue();
            borderColor = attrs.getAttr("shape_roundRect_borderColor").get().getColorValue();
            borderWidthPx = attrs.getAttr("shape_roundRect_borderWidth").get().getFloatValue();

        }
        borderPaint.setStyle(Paint.Style.STROKE_STYLE);
        super.setClipPathCreator(new ClipPathManager.ClipPathCreator() {
            @Override
            public Path createClipPath(int width, int height) {
                rect.fuse(0, 0, Float.intBitsToFloat(width), Float.intBitsToFloat(height));
                return generatePath(rect,
                    limitSize(topLeftRadius, width, height),
                    limitSize(topRightRadius, width, height),
                    limitSize(bottomRightRadius, width, height),
                    limitSize(bottomLeftRadius, width, height)
                );
            }

            @Override
            public boolean requiresBitmap() {
                return false;
            }
        });
    }

    /**
     * limitSize
     *
     * @param from from
     * @param width width
     * @param height height
     * @return float
     */
    protected float limitSize(float from, final float width, final float height) {
        return Math.min(from, Math.min(width, height));
    }

    @Override
    public void requiresShapeUpdate() {
        //borderRect.set(borderWidthPx / 2f, borderWidthPx / 2f, getWidth() - borderWidthPx / 2f, getHeight() - borderWidthPx / 2f);
        borderRect.fuse(borderWidthPx / 2f, borderWidthPx / 2f, getWidth() - borderWidthPx / 2f, getHeight() - borderWidthPx / 2f);
        borderPath.set(generatePath(borderRect,
            topLeftRadius,
            topRightRadius,
            bottomRightRadius,
            bottomLeftRadius
        ));
        super.requiresShapeUpdate();
    }


    private Path generatePath(RectFloat rect, float topLeftRadius, float topRightRadius, float bottomRightRadius, float bottomLeftRadius) {
        return generatePath(false, rect, topLeftRadius, topRightRadius, bottomRightRadius, bottomLeftRadius);
    }

    private Path generatePath(boolean useBezier, RectFloat rect, float topLeftRadius, float topRightRadius, float bottomRightRadius, float bottomLeftRadius) {
        final Path path = new Path();

        final float left = rect.left;
        final float top = rect.top;
        final float bottom = rect.bottom;
        final float right = rect.right;

        final float maxSize = Math.min(rect.getWidth() / 2f, rect.getHeight() / 2f);

        float topLeftRadiusAbs = Math.abs(topLeftRadius);
        float topRightRadiusAbs = Math.abs(topRightRadius);
        float bottomLeftRadiusAbs = Math.abs(bottomLeftRadius);
        float bottomRightRadiusAbs = Math.abs(bottomRightRadius);

        if (topLeftRadiusAbs > maxSize) {
            topLeftRadiusAbs = maxSize;
        }
        if (topRightRadiusAbs > maxSize) {
            topRightRadiusAbs = maxSize;
        }
        if (bottomLeftRadiusAbs > maxSize) {
            bottomLeftRadiusAbs = maxSize;
        }
        if (bottomRightRadiusAbs > maxSize) {
            bottomRightRadiusAbs = maxSize;
        }

        path.moveTo(left + topLeftRadiusAbs, top);
        path.lineTo(right - topRightRadiusAbs, top);

        //float left, float top, float right, float bottom, float startAngle, float sweepAngle, boolean forceMoveTo
        if (useBezier) {
            path.quadTo(right, top, right, top + topRightRadiusAbs);
        } else {
            final float arc = topRightRadius > 0 ? 90 : -270;
            path.arcTo(new RectFloat(right - topRightRadiusAbs * 2f, top, right, top + topRightRadiusAbs * 2f), -90, arc);
        }
        path.lineTo(right, bottom - bottomRightRadiusAbs);
        if (useBezier) {
            path.quadTo(right, bottom, right - bottomRightRadiusAbs, bottom);
        } else {
            final float arc = bottomRightRadiusAbs > 0 ? 90 : -270;
            path.arcTo(new RectFloat(right - bottomRightRadiusAbs * 2f, bottom - bottomRightRadiusAbs * 2f, right, bottom), 0, arc);
        }
        path.lineTo(left + bottomLeftRadiusAbs, bottom);
        if (useBezier) {
            path.quadTo(left, bottom, left, bottom - bottomLeftRadiusAbs);
        } else {
            final float arc = bottomLeftRadiusAbs > 0 ? 90 : -270;
            path.arcTo(new RectFloat(left, bottom - bottomLeftRadiusAbs * 2f, left + bottomLeftRadiusAbs * 2f, bottom), 90, arc);
        }
        path.lineTo(left, top + topLeftRadiusAbs);
        if (useBezier) {
            path.quadTo(left, top, left + topLeftRadiusAbs, top);
        } else {
            final float arc = topLeftRadiusAbs > 0 ? 90 : -270;
            path.arcTo(new RectFloat(left, top, left + topLeftRadiusAbs * 2f, top + topLeftRadiusAbs * 2f), 180, arc);
        }
        path.close();

        return path;
    }

    /**
     * getTopLeftRadius
     *
     * @return float
     */
    public float getTopLeftRadius() {
        return topLeftRadius;
    }

    /**
     * setTopLeftRadius
     *
     * @param topLeftRadius topLeftRadius
     */
    public void setTopLeftRadius(float topLeftRadius) {
        this.topLeftRadius = topLeftRadius;
        requiresShapeUpdate();
    }

    /**
     * getTopLeftRadiusDp
     *
     * @return float
     */
    public float getTopLeftRadiusDp() {
        return pxToDp(getTopLeftRadius());
    }

    /**
     * setTopLeftRadiusDp
     *
     * @param topLeftRadius topLeftRadius
     */
    public void setTopLeftRadiusDp(float topLeftRadius) {
        setTopLeftRadius(dpToPx(topLeftRadius));
    }

    /**
     * getTopRightRadius
     *
     * @return float
     */
    public float getTopRightRadius() {
        return topRightRadius;
    }

    /**
     * setTopRightRadius
     *
     * @param topRightRadius topRightRadius
     */
    public void setTopRightRadius(float topRightRadius) {
        this.topRightRadius = topRightRadius;
        requiresShapeUpdate();
    }

    /**
     * getTopRightRadiusDp
     *
     * @return float
     */
    public float getTopRightRadiusDp() {
        return pxToDp(getTopRightRadius());
    }

    /**
     * setTopRightRadiusDp
     *
     * @param topRightRadius topRightRadius
     */
    public void setTopRightRadiusDp(float topRightRadius) {
        setTopRightRadius(dpToPx(topRightRadius));
    }

    /**
     * getBottomRightRadius
     *
     * @return float
     */
    public float getBottomRightRadius() {
        return bottomRightRadius;
    }

    /**
     * setBottomRightRadius
     *
     * @param bottomRightRadius bottomRightRadius
     */
    public void setBottomRightRadius(float bottomRightRadius) {
        this.bottomRightRadius = bottomRightRadius;
        requiresShapeUpdate();
    }

    /**
     * getBottomRightRadiusDp
     *
     * @return float
     */
    public float getBottomRightRadiusDp() {
        return pxToDp(getBottomRightRadius());
    }

    /**
     * setBottomRightRadiusDp
     *
     * @param bottomRightRadius bottomRightRadius
     */
    public void setBottomRightRadiusDp(float bottomRightRadius) {
        setBottomRightRadius(dpToPx(bottomRightRadius));
    }

    /**
     * getBottomLeftRadius
     *
     * @return float
     */
    public float getBottomLeftRadius() {
        return bottomLeftRadius;
    }

    /**
     * setBottomLeftRadius
     *
     * @param bottomLeftRadius bottomLeftRadius
     */
    public void setBottomLeftRadius(float bottomLeftRadius) {
        this.bottomLeftRadius = bottomLeftRadius;
        requiresShapeUpdate();
    }

    /**
     * getBottomLeftRadiusDp
     *
     * @return float
     */
    public float getBottomLeftRadiusDp() {
        return pxToDp(getBottomLeftRadius());
    }

    /**
     * setBottomLeftRadiusDp
     *
     * @param bottomLeftRadius bottomLeftRadius
     */
    public void setBottomLeftRadiusDp(float bottomLeftRadius) {
        setBottomLeftRadius(dpToPx(bottomLeftRadius));
    }

    /**
     * getBorderColor
     *
     * @return Color
     */
    public Color getBorderColor() {
        return borderColor;
    }

    /**
     * setBorderColor
     *
     * @param borderColor borderColor
     */
    public void setBorderColor(Color borderColor) {
        this.borderColor = borderColor;
        requiresShapeUpdate();
    }

    /**
     * getBorderWidth
     *
     * @return float
     */
    public float getBorderWidth() {
        return borderWidthPx;
    }

    /**
     * setBorderWidth
     *
     * @param borderWidth borderWidth
     */
    public void setBorderWidth(float borderWidth) {
        this.borderWidthPx = borderWidth;
        requiresShapeUpdate();
    }

    /**
     * getBorderWidthDp
     *
     * @return float
     */
    public float getBorderWidthDp() {
        return pxToDp(getBorderWidth());
    }

    /**
     * setBorderWidthDp
     *
     * @param borderWidth borderWidth
     */
    public void setBorderWidthDp(float borderWidth) {
        setBorderWidth(dpToPx(borderWidth));
    }
}